using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Runtime.InteropServices;

namespace HCore
{
    public class ServerNetworkManager
    {
        private static readonly Dictionary<uint, ServerNetworkManager> Id2Manager =
            new Dictionary<uint, ServerNetworkManager>();

        private static uint NextManagerId = 1;

        public static ServerNetworkManager Get(int bindPort)
        {
            return new ServerNetworkManager(NextManagerId++, bindPort);
        }

        private static int OutPut(IntPtr buf, int len, IntPtr kcp, IntPtr user)
        {
            uint address = (uint) user.ToInt32();
            uint managerId = address >> NetworkDefine.SESSION_BITS & NetworkDefine.MANAGER_MASK;
            if (Id2Manager.TryGetValue(managerId, out var manager))
            {
                uint sessionId = address & NetworkDefine.SESSION_MASK;
                manager.Output(sessionId, buf, len);
            }

            return 0;
        }

        private readonly uint _id;
        private readonly UdpSocket _socket;
        private readonly Dictionary<uint, Session> _sessionId2sessions;
        private readonly Dictionary<EndPoint, Session> _endPoint2sessions;
        private readonly byte[] cacheBytes = new byte[128 * 1500];
        private readonly Stopwatch _stopwatch;
        private readonly Dictionary<byte, Action<SessionPacket>> _packetHandles;
        private uint _nextSessionId = 1;

        private ServerNetworkManager(uint id, int bindPort)
        {
            _id = id;
            _socket = new DelayUdpSocket(NetworkDefine.LOST_RATE, NetworkDefine.RTT_MIN, NetworkDefine.RTT_MAX, "server");
            _socket.Bind(new IPEndPoint(IPAddress.Any, bindPort));
            _sessionId2sessions = new Dictionary<uint, Session>();
            _endPoint2sessions = new Dictionary<EndPoint, Session>();
            _packetHandles = new Dictionary<byte, Action<SessionPacket>>();
            _stopwatch = new Stopwatch();
            _stopwatch.Start();

            Id2Manager.Add(_id, this);
        }

        public bool Register(byte packetType, Action<SessionPacket> handle)
        {
            if (_packetHandles.ContainsKey(packetType))
            {
                return false;
            }

            _packetHandles.Add(packetType, handle);
            return true;
        }

        public bool Unregister(byte packetType)
        {
            if (!_packetHandles.ContainsKey(packetType))
            {
                return false;
            }

            _packetHandles.Remove(packetType);
            return true;
        }

        public int Send(byte[] bytes, int length, uint sessionId)
        {
            if (_sessionId2sessions.TryGetValue(sessionId, out var session))
            {
                return session.Send(bytes, length);
            }

            return 0;
        }

        public int Send(OutputMemeryBitStream output, uint sessionId)
        {
            return Send(output.GetBuffer(), output.GetByteLength(), sessionId);
        }

        public void Update()
        {
            uint current = (uint) _stopwatch.ElapsedMilliseconds;
            foreach (var session in _sessionId2sessions)
            {
                session.Value.Update(current);
            }
        }

        private int RawSend(byte[] bytes, int length, EndPoint endPoint)
        {
            return _socket.SendTo(bytes, length, endPoint);
        }

        private int RawSend(OutputMemeryBitStream output, EndPoint endPoint)
        {
            return RawSend(output.GetBuffer(), output.GetByteLength(), endPoint);
        }

        public void Input()
        {
            while (true)
            {
                ReceivedPacket receivedPacket = _socket.ReceiveFrom();
                if (receivedPacket == null)
                {
                    break;
                }

                InputMemoryBitStream input = receivedPacket.input;
                uint conv = input.ReadUint();
                if (conv == NetworkDefine.CONNECT_MAGIC_NUM)
                {
                    //客户端请求建立连接
                    if (!_endPoint2sessions.TryGetValue(receivedPacket.endPoint, out var session))
                    {
                        session = new Session(_id, _nextSessionId++, receivedPacket.endPoint, OutPut);
                        _endPoint2sessions[session.endPoint] = session;
                        _sessionId2sessions[session.sessionId] = session;
                    }

                    OutputMemeryBitStream output = new OutputMemeryBitStream();
                    output.WriteUInt(NetworkDefine.CONNECT_MAGIC_NUM);
                    output.WriteUInt(session.sessionId);

                    RawSend(output, receivedPacket.endPoint);
                }
                else if (conv == NetworkDefine.CONNECTING_MAGIC_NUM)
                {
                    //已建立连接
                    if (_endPoint2sessions.TryGetValue(receivedPacket.endPoint, out var session))
                    {
                        session.Input(input.GetBuffer(), input.GetByteLength());
                    }
                }
            }

            foreach (var session in _sessionId2sessions)
            {
                Session s = session.Value;
                while (true)
                {
                    int readByteCount = s.Rec(cacheBytes, cacheBytes.Length);
                    if (readByteCount <= 0)
                    {
                        break;
                    }

                    var input = new InputMemoryBitStream(cacheBytes, readByteCount);
                    byte packetType = input.ReadByte();

                    if (!_packetHandles.TryGetValue(packetType, out var handle))
                    {
                        continue;
                    }

                    SessionPacket sessionPacket = new SessionPacket(input, s.sessionId);
                    handle(sessionPacket);
                }
            }
        }

        public int Output(uint sessionId, IntPtr buf, int len)
        {
            if (_sessionId2sessions.TryGetValue(sessionId, out var session))
            {
                Marshal.Copy(buf, cacheBytes, 0, len);
                return RawSend(cacheBytes, len, session.endPoint);
            }

            return 0;
        }
    }
}